package org.mtmi.ui;

import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Path;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.graphics.Region;
import org.eclipse.swt.graphics.Transform;

/**
 * Graphical facilities. 
 * @author jgout
 *
 */
public class GraphicUtil {

	/** 
	 * Returns the outline of the round-cornered rectangle specified by 
	 * the arguments as a path. 
	 * The left and right edges of the rectangle are at <code>x</code> and <code>x + width</code>. 
	 * The top and bottom edges are at <code>y</code> and <code>y + height</code>.
	 * The <em>roundness</em> of the corners is specified by the 
	 * <code>arcWidth</code> and <code>arcHeight</code> arguments, which
	 * are respectively the width and height of the ellipse used to draw
	 * the corners.
	 *
	 * @param gc the graphic context used to draw returned path.
	 * @param x the x coordinate of the rectangle to be drawn
	 * @param y the y coordinate of the rectangle to be drawn
	 * @param width the width of the rectangle to be drawn
	 * @param height the height of the rectangle to be drawn
	 * @param arcWidth the width of the arc
	 * @param arcHeight the height of the arc
	 * @return the path of the round rectangle.
	 */
	public static Path getRoundRectangle(GC gc, int x, int y, int width, int height, int arcWidth, int arcHeight) {
		Path p = new Path(gc.getDevice());
		if (width == 0 || height == 0) return p;									
		if (arcWidth == 0 || arcHeight == 0) {
			p.addRectangle(x, y, width, height);
			return p;
		}
		if (width < 0) {
			x += width;
			width = -width;
		}
		if (height < 0) {
			y += height;
			height = -height;
		}
		if (arcWidth < 0) arcWidth = -arcWidth;
		if (arcHeight < 0) arcHeight = -arcHeight;
		if (arcWidth > width) arcWidth = width;
		if (arcHeight > height) arcHeight = height;
				
		if (arcWidth <= width && arcHeight <= height) {
			p.moveTo(x, y);
			p.addArc(x, y, arcWidth, arcHeight, 90, 90);
			p.addArc(x, y+height-arcHeight, arcWidth, arcHeight, 180, 90); 
			p.addArc(x+width-arcWidth, y+height-arcHeight, arcWidth, arcHeight, -90, 90);
			p.addArc(x+width-arcWidth, y, arcWidth, arcHeight, 0, 90);
			p.close();
		}
		return p;
	}
	
	public static void drawRoundRectangle(GC gc, int x, int y, int width, int height, int arcWidth, int arcHeight) {
		Path p = getRoundRectangle(gc, x, y, width, height, arcWidth, arcHeight);
		gc.drawPath(p);
		p.dispose();
	}
	public static void fillRoundRectangle(GC gc, int x, int y, int width, int height, int arcWidth, int arcHeight) {
		Path p = getRoundRectangle(gc, x, y, width, height, arcWidth, arcHeight);
		gc.fillPath(p);
		p.dispose();
	}
	
	public static Region getRegion(Path p) {
		Region r = new Region();
		float[] fs = p.getPathData().points;
		int[] points = new int[fs.length];
		for (int i = 0; i < fs.length; i++) {
			points[i] = (int)fs[i];
		}
		r.add(points);
		return r;
	}
	
	/** @return true if the rectangle contains point */
	public static boolean rectContains( int x, int y, int w, int h, int px, int py ) {
		return x<=px && px<=x+w && y<=py && py <= y+h;
	}
	
	/** @return true if the rectangle contains point */
	public static boolean rectContains( Point location, Point size, int px, int py ) {
		return location.x<=px && px<=location.x+size.x && location.y<=py && py <= location.y+size.y;
	}
	
	/** @return true if the rectangle contains point */
	public static boolean rectContains( Rectangle r, int px, int py ) {
		return r.x<=px && px<=r.x+r.width && r.y<=py && py <= r.y+r.height;
	}
	
	/** @return true if circle defined by center and radius contains the given point */
	public static boolean circleContains( int cx, int cy, int r, int px, int py ) {
		int dx = px-cx, dy = py-cy;
		int er2 = dx*dx+dy*dy;
		return er2 <= r*r;	
	}
	

	/** 
	 * Initialize a rectangle with given parameter.
	 * @param r if null create a new rectangle, otherwise fill r's fields with values
	 * @return the rectangle with given bounds
	 */
	public static Rectangle setRect( Rectangle r, int x, int y, int w, int h ) {
		if( r==null ) {
			return new Rectangle( x, y, w, h );
		} else {
			r.x = x; r.y=y; r.width=w; r.height=h;
			return r;
		}
	}
	
	/** expand r rectangle bounds to include point, if outside rectangle */
	public static void expandRect( Rectangle r, int x, int y ) {
		int l = r.x, d = r.x+r.width;
		int t = r.y, b = r.y+r.height;
		l = Math.min( l, x );
		d = Math.max( d, x );
		t = Math.min( t, y );
		b = Math.max( b, y );
		r.x = l; 
		r.width = d-l;
		r.y = t;
		r.height = b-t;
	}
	
	/** expand r rectangle bounds to include rectangle a */
	public static void expandRect( Rectangle r, Rectangle a ) {
		expandRect( r, a.x, a.y );
		expandRect( r, a.x+a.width, a.y+a.height );
	}
	
	/** expand r rectangle bounds to include rectangle (location,size) */
	public static void expandRect( Rectangle r, Point location, Point size ) {
		expandRect( r, location.x, location.y );
		expandRect( r, location.x+size.x, location.y+size.y );
	}
	
	/** expand r rectangle bounds to include given rectangle */
	public static void expandRect( Rectangle r, int x, int y, int w, int h ) {
		expandRect( r, x, y );
		expandRect( r, x+w, y+h );
	}

	/** expand rectangle by 'size' on all direction left,top,right,bottom), size must be >0 */
	public static void expandRect( Rectangle r, int size ) {
		r.x -= size;
		r.y -= size;
		r.width += 2*size; 
		r.height += 2*size; 
	}
	
	/**
	 * @see #pointToCenterOutlinePoint(int, int, int, int, int, int);
	 */
	public static Point pointToCenterOutlinePoint( Rectangle r, int x, int y )
	{
		return  pointToCenterOutlinePoint( r.x, r.y, r.width, r.height, x, y );
	}
	
	/**
	 * @see #pointToCenterOutlinePoint(int, int, int, int, int, int);
	 */
	public static Point pointToCenterOutlinePoint( Point location, Point size, int x, int y )
	{
		return  pointToCenterOutlinePoint( location.x, location.y, size.x, size.y, x, y );
	}
	
	/**
	 * Return the rectangle outline point which is the contact of rectangle and a segment from
	 * given point to center of rectangle. The given point MUST be outside, otherwise the given point is returned 
	 * @return the point along rectangle outline where line (x,y)-(rectangle center) pass through outline.
	 */
	public static Point pointToCenterOutlinePoint( int rx, int ry, int rw, int rh, int x, int y )
	{
		int left, top, right, bottom;
		if( rw >=0 ) {
			left = rx;
			right = rx+rw;
		} else {
			left = rx+rw;
			right = rx;
		}
		if( rh>=0 ) {
			top = ry;
			bottom = ry+rh;
		} else {
			top = ry+rh;
			bottom = ry;
		}
		
		Point p = new Point(0,0);
		int cx = (left+right)/2;
		int cy = (top+bottom)/2;
		
		if( y >= top && y <= bottom && x >= left && x <= right ) {
			//point is inside rectangle: return it.
			p.x=x;
			p.y=y;
		} else if( y == cy ) {
			p.y = cy;
			if( x < cx ) {
				p.x = left;
			} else {
				p.x = right;
			}
		}
		else if( x == cx ) {
			p.x = cx;
			if( y < top ) {
				p.y = top;
			} else {
				p.y = bottom;
			}
		}
		else if( y >= top && y <= bottom ){
			if( x < cx ) {
				p.x = left;
			} else {
				p.x = right;
			}
			p.y = (y-cy)*(p.x-cx)/(x-cx)+cy;
		}
		else if( x >= left && x <= right ) {
			if( y < cy ) {
				p.y = top;
			} else {
				p.y = bottom;
			}
			p.x = (x-cx)*(p.y-cy)/(y-cy)+cx;
		}
		else if( x < left && y < top ) {
			int yl = (y-cy)*(left-cx)/(x-cx)+cy;
			if( yl >= top ) {
				p.x = left;
				p.y = yl;
			} else {
				p.y = top;
				p.x = (x-cx)*(p.y-cy)/(y-cy)+cx;
			}
		}
		else if( x >= right && y < top ) {
			int yr = (y-cy)*(right-cx)/(x-cx)+cy;
			if( yr >= top ) {
				p.x = right;
				p.y = yr;
			} else {
				p.y = top;
				p.x = (x-cx)*(p.y-cy)/(y-cy)+cx;
			}
		}
		else if( x <= left && y >= bottom ) {
			int yl = (y-cy)*(left-cx)/(x-cx)+cy;
			if( yl <= bottom ) {
				p.x = left;
				p.y = yl;
			} else {
				p.y = bottom;
				p.x = (x-cx)*(p.y-cy)/(y-cy)+cx;
			}
		}
		else if( x >= right && y >= bottom ) {
			int yr = (y-cy)*(right-cx)/(x-cx)+cy;
			if( yr <= bottom ) {
				p.x = right;
				p.y = yr;
			} else {
				p.y = bottom;
				p.x = (x-cx)*(p.y-cy)/(y-cy)+cx;
			}
		}
		return p;
	}
	
	/**
	 * Draw an arrow in segment 1->2 at relative (1) location k (i.e. 0 is (1), 0.5f is middle).
	 * Using gc's foreground and background values.
	 */ 
	public static void drawArrow( GC gc, int x1, int y1, int x2, int y2, float k, int dim )
	{
		int xy[] = new int[] {
				-dim, -dim,
				dim, 0,
				-dim, +dim,
		};
		int dx = x2-x1, dy = y2-y1;
		double angle_rad = Math.atan2( dy, dx );
		
		int tx = Math.round( x1+k*dx );
		int ty = Math.round( y1+k*dy );
			
		Transform save = new Transform( gc.getDevice() );
		gc.getTransform( save );
		Transform tf = new Transform( gc.getDevice() );
		float angle_deg = (float)(180*angle_rad/Math.PI);
		tf.translate( tx, ty );
		tf.rotate( angle_deg );
		gc.setTransform( tf );
		gc.fillPolygon( xy );
		gc.drawPolygon( xy );
		gc.setTransform( save );
	}
	
	/** @return the distance from P point to line (A,B) */
	public static int distPointToLine( int xa, int ya, int xb, int yb, int xp, int yp ) {
		if( xa==xb ) { //vertical line:
			return Math.abs(xp-xa);
		}
		else if( ya==yb ) { //horizontal line:
			return Math.abs(yp-ya);
		}
		else { //line is a.x + b.y + c=0
			
			float a = (ya-yb)/(float)(xa-xb);
			//note: int b = -1;
			float c = ya-a*xa;
			
			//dist:
			int d = (int)( Math.abs( a*xp-yp+c ) / Math.sqrt( a*a+1 ) );
			return d;
		}
	}
	
	/** @return true if both rectangle overlap (have non empty union) */
	public static boolean rectOverlap( Rectangle r1, Rectangle r2 ) {
		boolean h_overlap = r2.x <= r1.x+r1.width && r2.x+r2.width > r1.x;
		if( !h_overlap ) return false;
		boolean v_overlap = r2.y <= r1.y+r1.height && r2.y+r2.height > r1.y;
		return v_overlap;
	}
	
}
